package com.seednia.foundation.framework.dao;

import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.beanutils.BeanUtils;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;

import com.seednia.foundation.framework.dto.AbstractRecord;
import com.seednia.foundation.framework.dto.JoinParams;
import com.seednia.foundation.framework.dto.QueryParams;
import com.seednia.foundation.framework.service.DataAccessException;

/**
 * Base class for all Hibernate implementation of DAO
 * 
 * @author Young Suk Ahn
 *
 */
public abstract class AbstractDaoHibernate extends AbstractDao {

	protected SessionFactory sessionFactory = null;
	
	public AbstractDaoHibernate()
	{
		super(null, null, null);
	}
	
	public AbstractDaoHibernate(String tableName, List<String> selectProperties, List<String> selectColumns)
	{
		super(tableName, selectProperties, selectColumns);
	}

	public SessionFactory getSessionFactory() {
		return sessionFactory;
	}

	public void setSessionFactory(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}

	protected Session getCurrentSession()
	{
		return this.sessionFactory.getCurrentSession();
	}
	
	public int deleteHql(String tableName, String whereClause, Map<String, Object> whereClauseParams)
			throws DataAccessException
	{
		try {
			Session session = this.getCurrentSession();
			String hqlQuery = "DELETE FROM "+tableName+" WHERE "+whereClause;
			Query query = session.createQuery(hqlQuery);
			
			for (Entry<String, Object> entry: whereClauseParams.entrySet()) {
				query.setParameter(entry.getKey(), entry.getValue());
			}
			
			int retval = query.executeUpdate();

			return retval;
		} catch (Exception e) {
			throw new DataAccessException(e);
		}
	}

	public List selectJoin(JoinParams joinParams, String whereClause, QueryParams params) throws DataAccessException
	{
		return selectJoinSql(joinParams, whereClause, params);
	}
	
	protected List selectJoinHql(JoinParams joinParams, String whereClause, QueryParams params) throws DataAccessException
	{
		//throw new UnsupportedOperationException();
		Session session = getCurrentSession(); 
		
		StringBuffer joinedColsCsv = new StringBuffer();
		StringBuffer joinedTablesCsv = new StringBuffer();
		for(JoinParams.JoinTable joinTable: joinParams.getJoinTables()) {
			joinedTablesCsv.append(joinTable.getJointType()).append(" JOIN ").append(joinTable.getName()).append(" ").append(joinTable.getAlias()).append(", ");
			for(JoinParams.Column selectCol: joinTable.getSelectColumns()) {
				joinedColsCsv.append(joinTable.getAlias()).append(".").append(selectCol.getName()).append(", ");
			}
		}
		if (joinedTablesCsv.length() > 2)
			joinedTablesCsv.delete(joinedTablesCsv.length()-2, joinedTablesCsv.length()-1);
		if (joinedColsCsv.length() > 2)
			joinedColsCsv.delete(joinedColsCsv.length()-2, joinedColsCsv.length()-1);
		
		
		Query query = session.createQuery("SELECT "+super.getSelectColumnsCsv()+", "+ joinedColsCsv
				+" from "+ super.getTableName()
				+" this "+joinedTablesCsv+" WHERE "+ whereClause);
		
		for (Entry<String, Object> entry: params.entrySet()) {
			query.setParameter(entry.getKey(), entry.getValue());
		}
        
        return query.list();
	}
	
	protected List selectJoinSql(JoinParams joinParams, String whereClause, QueryParams params) throws DataAccessException
	{
		Session session = getCurrentSession(); 
		
		StringBuffer joinedColsCsv = new StringBuffer();
		StringBuffer joinedTablesCsv = new StringBuffer();
		for(JoinParams.JoinTable joinTable: joinParams.getJoinTables()) {
			// Compose list of columns to retrieve: 
			for(JoinParams.Column selectCol: joinTable.getSelectColumns()) {
				joinedColsCsv.append(joinTable.getAlias()).append(".").append(selectCol.getName());
				if (selectCol.getAlias() != null) 
					joinedColsCsv.append(" AS ").append(selectCol.getAlias());
				joinedColsCsv.append(", ");
			}
			// Compose join clause:
			joinedTablesCsv.append(" ").append(joinTable.getJointType()).append(" JOIN ").append(joinTable.getName()).append(" ").append(joinTable.getAlias()).append(" ")
				.append(" ON ").append(joinTable.getOnClause());
		}
		if (joinedColsCsv.length() > 2)
			joinedColsCsv.delete(joinedColsCsv.length()-2, joinedColsCsv.length()-1);
		
		StringBuffer sqlQueryStr = new StringBuffer("SELECT ");
		sqlQueryStr.append(super.getSelectColumnsCsv()).append(", ").append(joinedColsCsv)
			.append(" from ").append(super.getTableName()).append(" this ").append(joinedTablesCsv);
			
		if (whereClause.trim().length() > 0)
			sqlQueryStr.append(" WHERE ").append(whereClause);

		Query query = session.createSQLQuery(sqlQueryStr.toString());
		
		for (Entry<String, Object> entry: params.entrySet()) {
			query.setParameter(entry.getKey(), entry.getValue());
		}
        
        return query.list();
	}
	
	protected int loadRecord(AbstractRecord dest, Object[] source)
	{
		int colIdx = 0;
		for (String propName : super.getSelectProperties()) {
			try {
				if (source[colIdx] != null)
					BeanUtils.setProperty(dest, propName, source[colIdx]);
				colIdx++;
			} catch (Exception e) {
				throw new IllegalArgumentException(e);
			} 
		}
		return colIdx;
	}
	

	/**
	 * Updates a particular field
	 * @param fieldName
	 * @param fieldValue
	 * @param wherePredicate
	 * @return
	 * @throws DataAccessException
	 */
	protected int updateField(String fieldName, Object fieldValue, String wherePredicate) throws DataAccessException
	{
		Session session = getCurrentSession(); 
		Query query = session.createSQLQuery("UPDATE "+ this.getTableName() 
				+ " SET " + fieldName+" = :" + fieldName + " WHERE "+ wherePredicate);
		
		query.setParameter(fieldName, fieldValue);
		return  query.executeUpdate();
	}
	
	/**
	 * Updates a particular field
	 * @param fieldName
	 * @param fieldValue
	 * @param wherePredicate
	 * @return
	 * @throws DataAccessException
	 */
	protected int updateFields(Map<String, Object> values, String wherePredicate) throws DataAccessException
	{
		Session session = getCurrentSession(); 
		
		StringBuffer strSqlStmt = new StringBuffer("UPDATE ");
		strSqlStmt.append( this.getTableName() ).append(" SET ");
		boolean isFirst = true;
		for (Map.Entry<String, Object> entry: values.entrySet()) {
			if ( isFirst ) {
				isFirst = false;
			} else {
				strSqlStmt.append(", ");
			}
			strSqlStmt.append( entry.getKey() ).append(" = :").append(entry.getKey());
		}
		strSqlStmt.append(" WHERE ").append(wherePredicate);
		
		Query query = session.createSQLQuery(strSqlStmt.toString());
		
		for (Map.Entry<String, Object> entry: values.entrySet()) {
			query.setParameter(entry.getKey(), entry.getValue());
		}
		return  query.executeUpdate();
	}
	
	/**
	 * Increments by the step of a numeric field
	 * @param fieldName
	 * @param step
	 * @param wherePredicate
	 * @return
	 * @throws DataAccessException
	 */
	public int incrementField(String fieldName, int step, String wherePredicate) throws DataAccessException
	{
		Session session = getCurrentSession(); 
		
		String operator = " + ";
		if (step < 0) {
			operator = " = ";
			step = -step;
		}
		Query query = session.createSQLQuery("UPDATE "+ this.getTableName() 
				+ " SET " + fieldName+ "=:" + fieldName + operator +step + " WHERE "+ wherePredicate);
		
		query.setParameter(fieldName, step);
		
		return  query.executeUpdate();
	}
}
